﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;

namespace DARE
{
    /// <summary>
    /// The CGameObjectMgr is a way to manage game objects.
    /// It will auto-update each game object registered (or instance of game object created by the Instantiate method).
    /// </summary>
    public class CGameObjectMgr: IUpdateable
    {
        #region fields

        /// <summary>
        /// Set this value to true to enable the gameObjectManager
        /// </summary>
        private bool m_updateable = false;

        /// <summary>
        /// This is the last id given to a GameObject instance
        /// </summary>
        /// <seealso cref="LastDisponibleId"/>
        private uint m_ids;

        /// <summary>
        /// There are the GameObject models, which must be instantiated by the Instantiate method to be used in a game
        /// </summary>
        /// <seealso cref="m_gameObjectInstances"/>
        /// <seealso cref="RegisterGameObject"/>
        /// <seealso cref="Instantiate"/>
        private Dictionary<string, CGameObject> m_gameObjects;

        /// <summary>
        /// This is the list of any instantiated GameObjects models
        /// </summary>
        /// <seealso cref="m_gameObjects"/>
        /// <seealso cref="Instantiate"/>
        private Dictionary<uint, CGameObject> m_gameObjectInstances;


        #endregion

        #region properties

        public CRender3D Render3D { get; set; }

        /// <summary>
        /// Returns the last disponible id and increments the id value. 
        /// </summary>
        public uint LastDisponibleId { get { ++m_ids; return m_ids - 1; } }

        public bool Updatable
        {
            get { return m_updateable; }
            set { m_updateable = value; }
        }

        #endregion

        #region ctor

        public CGameObjectMgr(CRender3D render)
        {
            Render3D = render;
            m_ids = 1;
            m_gameObjectInstances = new Dictionary<uint, CGameObject>();
            m_gameObjects = new Dictionary<string, CGameObject>();
        }

        #endregion

        #region methods

        public void InitializeUninitializedInstances()
        {
            foreach (CGameObject instance in m_gameObjectInstances.Values)
                if (!instance.IsInitialized)
                    instance.Initialize();
        }

        public void Update(GameTime gameTime)
        {
            for (int i = 0; i < m_gameObjectInstances.Count; ++i)
                if (m_gameObjectInstances.ElementAt(i).Value.IsActive)
                    m_gameObjectInstances.ElementAt(i).Value.Update();
        }

        /// <summary>
        /// Register a GameObject as instantiation (as if it was instantiated with the Instantiate method).
        /// If an entry with the same ID is already registered, this will replace the old one with the new one.
        /// </summary>
        /// <param name="instantiation">the instance to register</param>
        /// <returns>the instantiation registered</returns>
        public CGameObject RegisterInstantiation(CGameObject instance)
        {
            if (!m_gameObjectInstances.ContainsKey(instance.ID))
                m_gameObjectInstances.Add(instance.ID, instance);
            else
                m_gameObjectInstances[instance.ID] = instance;
            instance.Mgr = this; 
            return instance;
        }

        public CGameObject UnregisterInstantiation(CGameObject instance)
        {
            m_gameObjectInstances.Remove(instance.ID);
            instance.Mgr = null;
            return instance;
        }

        /// <summary>
        /// Registers a GameObject as model so it can be Instantiated.
        /// </summary>
        /// <param name="gameObject">the GameObject to register</param>
        /// <returns>the GameObject registered</returns>
        public CGameObject RegisterGameObject(CGameObject gameObject)
        {
            m_gameObjects.Add(gameObject.Name, gameObject);
//            gameObject.Node.SetActive(false);
            gameObject.Mgr = this;
            return gameObject;
        }

        /// <summary>
        /// Finds the list of GameObject that have the tag given in parameters (only Instantiated GameObjects)
        /// </summary>
        /// <param name="tag">the tag to compare with existent tags</param>
        /// <returns>the list of GameObject Instantiated with the same tag as given in parameters</returns>
        /// <seealso cref="FindGameObjectByTag"/>
        public List<CGameObject> FindInstanceByTag(string tag)
        {
            List<CGameObject> objs = new List<CGameObject>();
            foreach (CGameObject obj in m_gameObjectInstances.Values)
                if (obj.Tag == tag)
                    objs.Add(obj);
            return objs;
        }

        /// <summary>
        /// Finds the list of GameObject that have the tag given in parameters (only non Instantiated GameObjects)
        /// </summary>
        /// <param name="tag">the tag to compare with existent tags</param>
        /// <returns>the list of GameObject with the same tag as given in parameters</returns>
        /// <seealso cref="FindInstanceByTag"/>
        public List<CGameObject> FindGameObjectByTag(string tag)
        {
            List<CGameObject> objs = new List<CGameObject>();
            foreach (CGameObject obj in m_gameObjects.Values)
                if (obj.Tag == tag)
                    objs.Add(obj);
            return objs;
        }

        /// <summary>
        /// Finds the GameObject that have the unique ID given in parameters (only the Instantiated, to find a GameObject model, use FindGameObject(string name)).
        /// </summary>
        /// <param name="id">The ID of the Instantiated GameObject</param>
        /// <returns>the GameObject corresponding to the given ID</returns>
        /// <seealso cref="FindGameObject"/>
        public CGameObject FindInstance(uint id)
        {
            if (m_gameObjectInstances.ContainsKey(id))
                return m_gameObjectInstances[id];
            return null;
        }

        /// <summary>
        /// Finds the GameObject that have the node given in parameters (only the Instantiated, to find a GameObject model, use FindGameObject(string name)).
        /// </summary>
        /// <param name="node">The node of the Instantiated GameObject</param>
        /// <returns>the GameObject corresponding to the given ID</returns>
        /// <seealso cref="FindGameObject"/>
        public CGameObject FindInstance(CNode node)
        {
            if (node == null)
                return null;
            foreach (CGameObject obj in m_gameObjectInstances.Values)
                if (obj.Node != null && obj.Node.Name == node.Name)
                    return obj;
            return null;
        }

        /// <summary>
        /// Finds an instance by the Vignette associated.
        /// </summary>
        public CGameObject FindInstanceByVignette(CVignette vignette)
        {
            if (vignette == null)
                return null;
            foreach (CGameObject obj in m_gameObjectInstances.Values)
                if (obj.Vignette != null && obj.Vignette == vignette)
                    return obj;
            return null;
        }

        /// <summary>
        /// Finds the list of GameObject that have the nameD given in parameters (only the Instantiated, to find a GameObject model, use FindGameObject(string name)).
        /// </summary>
        /// <param name="name">The name of the Instantiated GameObject</param>
        /// <returns>the list of GameObject corresponding to the given name</returns>
        /// <seealso cref="FindGameObject"/>
        public List<CGameObject> FindInstances(string name)
        {
            List<CGameObject> objs = new List<CGameObject>();
            foreach (CGameObject obj in m_gameObjectInstances.Values)
                if (obj.Name == name)
                    objs.Add(obj);
            return objs;
        }

        /// <summary>
        /// Gets all Instantiated GameObjects
        /// </summary>
        public List<CGameObject> GetAllInstances()
        {
            return m_gameObjectInstances.Values.ToList();
        }

        /// <summary>
        /// Gets all GameObject templates
        /// </summary>
        public List<CGameObject> GetAllGameObjectTemplates()
        {
            return m_gameObjects.Values.ToList();
        }

        /// <summary>
        /// Finds the GameObject with the name given in parameters (only the model, to find a GameObject instantiated (created from Instantiate(...)), use FindInstances(string name)).
        /// </summary>
        /// <param name="name">The name of the GameObject</param>
        /// <returns>The GameObject with the given name</returns>
        public CGameObject FindGameObject(string name)
        {
            if (m_gameObjects.ContainsKey(name))
                return m_gameObjects[name];
            return null;
        }

        /// <summary>
        /// Instantiate the GameObject given in parameters (only if this one isn't an Instantiation itself).
        /// </summary>
        /// <param name="gameObject">the GameObject to instantiate</param>
        /// <param name="name">the name of the instantiated GameObject.
        /// If this parameter is null or empty, this method will generate a name with the combination
        /// of its model name, "clone", and its ID.</param>
        /// <param name="position">the initial position of the instantiated GameObject</param>
        /// <param name="orientation">the initial orientation of the instantiated GameObject</param>
        /// <returns>The instantiated GameObject</returns>
        public CGameObject Instantiate(CGameObject gameObject, string name, Vector3 position, Quaternion orientation)
        {
            if (gameObject != null && m_gameObjects.ContainsValue(gameObject))
            {
                CGameObject go = gameObject.Clone() as CGameObject;
                if (name == null || name == "")
                {
                    name = gameObject.Name + "_Clone_" + go.ID;
                }
                go.Name = name;
                go.IsActive = true;
                if (go.Node != null)
                {
                    go.Node.IsActive = true;
                    go.Node.SetName(name);
                    if (go.Node.Entity.Physic != null)
                        go.Node.Entity.Physic.Body.MoveTo(position, Matrix.CreateFromQuaternion(orientation));
                    else
                    {
                        go.Node.Position = position;
                        go.Node.Orientation = orientation;
                    }
                }
                if (!m_gameObjectInstances.ContainsKey(go.ID))
                    m_gameObjectInstances.Add(go.ID, go);
                else
                    m_gameObjectInstances[go.ID] = go;
                return go;
            }
            return null;
        }

        /// <summary>
        /// Instantiate the GameObject associated to the name given in parameters (only if this one isn't an Instantiation itself).
        /// </summary>
        /// <param name="srcName">the name of the GameObject to instantiate</param>
        /// <param name="name">the name of the instantiated GameObject.
        /// If this parameter is null or empty, this method will generate a name with the combination
        /// of its model name, "clone", and its ID.</param>
        /// <param name="position">the initial position of the instantiated GameObject</param>
        /// <param name="orientation">the initial orientation of the instantiated GameObject</param>
        /// <returns>The instantiated GameObject</returns>
        public CGameObject Instantiate(string srcName, string name, Vector3 position, Quaternion orientation)
        {
            if (m_gameObjects.ContainsKey(srcName))
                return Instantiate(m_gameObjects[srcName], name, position, orientation);
            return null;
        }

        public void Destroy(CGameObject gameObject)
        {
            gameObject.Destroy();
        }

        public void Clear()
        {
            m_gameObjects.Clear();
            m_gameObjectInstances.Clear();
            m_ids = 1;
        }

        #endregion
    }
}
